/**
 * Created with JetBrains PhpStorm.
 * User: shaoshuai
 * Date: 12-7-18
 */
(function (window, jQuery) {
    /**
     * ZenJS 一个微型 javascript mvc 框架
     *
     * 按照ZenJS的组织方式写代码，结构清晰易于维护
     * ZenJS的mvc概念类似原始mvc模式，不同于backbone ExtJS 4 和 php框架 或者 java中的 ssh 这些都是变化过的mvc
     *
     * Zen要求一个Controller对应一个View，Model不受限制
     * Controller只接收事件并分别控制View和Model，每个控制器都有掌控全局html的能力，可以通过control方法结合jquery选择器
     * View只负责处理和DOM相关的逻辑，View监听Model的变更，View受Controller控制，View不受Model控制，View和Model解耦合
     * Model只负责Ajax和数据处理，Model触发事件
     *
     * @namespace
     */
    var Zen = {
        version: '0.1'
    };

    Zen.apply = function (o, c) {
        if (o && c && typeof c === 'object') {
            for (var p in c) {
                if (c.hasOwnProperty(p)) {
                    o[p] = c[p];
                }
            }
        }
        return o;
    };

    Zen.apply(Zen, /** @lends Zen */ {
        /**
         * @param parent
         * @param {Object} c
         * @return {Function}
         */
        extend: function (parent, c) {
            var F = function () {
            };
            F.prototype = parent.prototype;
            var f = new F();
            this.apply(f, c);
            var child;

            if (c.constructor && c.constructor !== Object.prototype.constructor) {
                child = c.constructor;
            } else {
                child = function () {
                    parent.apply(this, arguments);
                };
            }
            child.superclass = parent.prototype;
            child.prototype = f;
            child.prototype.constructor = child;
            return child;
        },
        each: function (array, fn, scope) {
            if (Object.prototype.toString.call(array) === '[object Array]') {
                for (var i = 0; i < array.length; i++) {
                    if (fn.call(scope || array[i], array[i], i) === false) {
                        return i;
                    }
                }
            }
        },
        /**
         * check if mixed is Array
         * @param {Object} mixed
         * @return {Boolean}
         */
        isArray: function (mixed) {
            if (Array.isArray) {
                return Array.isArray(mixed);
            } else {
                return (Object.prototype.toString.call(mixed) === '[object Array]');
            }
        },
        /**
         * @param {Object} mixed
         * @return {Boolean}
         */
        isFunction: function (mixed) {
            return typeof mixed == 'function';
        },
        isDefined: function (v) {
            return typeof v !== 'undefined';
        },
        applyIf: function (o, c) {
            if (o) {
                for (var p in c) {
                    if (c.hasOwnProperty(p) && !Zen.isDefined(o[p])) {
                        o[p] = c[p];
                    }
                }
            }
            return o;
        },
        /**
         * @param {String} namespace
         * @return {Zen}
         */
        namespace: function (namespace) {
            var arr = namespace.split('.');
            var root = window;
            Zen.each(arr, function (item) {
                if (!Zen.isDefined(root[item])) {
                    root[item] = {};
                }
                root = root[item];
            });
            return this;
        },
        getConstructorByName: function (constructorName) {
            var arr = constructorName.split('.');
            var root = window;
            for (var i = 0; i < arr.length; i++) {
                if (Zen.isDefined(root[arr[i]])) {
                    root = root[arr[i]];
                } else {
                    return null;
                }
            }
            return root;
        }
    });

    Zen.applyIf(Array.prototype, {
        indexOf: function (searchElement, fromIndex) {
            var len = this.length;
            fromIndex = fromIndex || 0;
            fromIndex += (fromIndex < 0) ? len : 0;
            for (; fromIndex < len; ++fromIndex) {
                if (this[fromIndex] === searchElement) {
                    return fromIndex;
                }
            }
            return -1;
        },
        /**
         * Removes the specified object from the array.  If the object is not found nothing happens.
         * @param {Object} o The object to remove
         * @return {Array} this array
         */
        remove: function (o) {
            var index = this.indexOf(o);
            if (index !== -1) {
                this.splice(index, 1);
            }
            return this;
        }
    });

    /**
     * @class Zen.Event
     * @param {Zen.Observable} owner
     * @param {String} name
     * @constructor
     */
    Zen.Event = function (owner, name) {
        /**
         * @type {Zen.Observable}
         */
        this.owner = owner;
        /**
         *
         * @type {String}
         */
        this.name = name;
        this.listeners = [];
    };
    /**
     * @param {Function} fn
     * @param {Object} scope
     */
    Zen.Event.prototype.addListener = function (fn, scope) {
        this.listeners.push({
            fn: fn,
            scope: scope
        });
    };
    /**
     * @param {Array} args
     */
    Zen.Event.prototype.fire = function (args) {
        for (var i = 0; i < this.listeners.length; i++) {
            var l = this.listeners[i];
            l.fn.apply(l.scope || this.owner, args);
        }
    };

    /**
     * @class Zen.Observable
     * @constructor
     */
    Zen.Observable = function () {
        this.events = {};
    };
    /**
     * @lends Zen.Observable
     * @type {Object}
     */
    Zen.Observable.prototype = {
        addEvent: function (eventName) {
            if (!this.events[eventName]) {
                this.events[eventName] = new Zen.Event(this, eventName);
            }
            return this.events[eventName];
        },
        addEvents: function () {
            for (var i = 0; i < arguments.length; i++) {
                this.addEvent(arguments[i]);
            }
        },
        addListener: function (eventName, fn, scope) {
            this.addEvent(eventName).addListener(fn, scope);
        },
        fireEvent: function (eventName) {
            var args = Array.prototype.slice.call(arguments, 1);
            this.events[eventName] && this.events[eventName].fire(args);
        }
    };
    Zen.Observable.prototype.on = Zen.Observable.prototype.addListener;
    Zen.Observable.prototype.constructor = Zen.Observable;

    /**
     * @class Zen.Controller
     * @constructor
     */
    Zen.Controller = Zen.extend(Zen.Observable, /** @lends Zen.Controller */ {
        constructor: function () {
            Zen.Controller.superclass.constructor.call(this);
            this.initController();
        },
        initController: function () {
        },
        /**
         * @param {String} selector
         * @param {String} eventType
         * @param {String} actionName
         * @param {Object} [scope]
         */
        control: function (selector, eventType, actionName, scope) {
            var that = this;
            var target = jQuery(selector);
            if ((typeof this[actionName]).toLowerCase() === 'function') {
                var action = this[actionName];
                target.bind(eventType, function () {
                    var args = [this];
                    for (var i = 0; i < arguments.length; i++) {
                        args.push(arguments[i]);
                    }
                    action.apply(scope || that, args);
                });
            }
        }
    });

    /**
     * @class Zen.View
     * @constructor
     */
    Zen.View = Zen.extend(Zen.Observable, /** @lends Zen.View */ {
        constructor: function (config) {
            Zen.View.superclass.constructor.call(this);
            config = config || {};
            if (config['el']) {
                this.el = jQuery(config['el']);
            } else if (this.el) {
                this.el = jQuery(this.el);
            }
            this.initView.call(this, config);
        },
        initView: function () {
        },
        getEl: function () {
            return this.el;
        }
    });
    /**
     * @param {Function} constructor
     * @param {Object} [config]
     * @return {*}
     */
    Zen.View.singleton = function (constructor, config) {
        if (!constructor.__instance__) {
            constructor.__instance__ = new constructor(config);
        }
        return constructor.__instance__;
    };
    /**
     * @param {Function} constructor
     * @param {Object} [config]
     * @return {*}
     */
    Zen.View.instance = function (constructor, config) {
        return new constructor(config);
    };

    /**
     * @class Zen.Model
     * @constructor
     */
    Zen.Model = Zen.extend(Zen.Observable, /** @lends Zen.Model */ {
        constructor: function () {
            Zen.Model.superclass.constructor.call(this);
            this.initModel.call(this);
        },
        initModel: function () {
        },
        /**
         * a built in function for jQuery.ajax
         * @param {Object} settings
         */
        ajax: function (settings) {
            if (typeof settings.error !== 'function') {
                settings.error = function (xhr, textStatus) {
                    alert('Network error: ' + textStatus);
                }
            }
            jQuery.ajax(settings);
        }
    });


    var controllerCache = {};
    Zen.Application = function (config) {
        if (config && config['name']) {
            var appName = config['name'];
            var appFolder = 'app';
            var controllers = config['controllers'];
            var views = config['views'];
            var models = config['models'];

            //auto create base namespaces
            Zen.namespace(appName + '.controller').namespace(appName + '.view').namespace(appName + '.model');

            if (Zen.isArray(controllers)) {
                var beforeLaunch = function () {
                    Zen.each(controllers, function (controllerName) {
                        if (!controllerCache[controllerName]) {
                            controllerCache[controllerName] = new (Zen.getConstructorByName(appName + '.controller.' + controllerName))();
                        }
                    });
                    if (Zen.isFunction(config['launch'])) {
                        config['launch'].call(window);
                    }
                };
                var list = [];
                Zen.each(controllers, function (controllerName) {
                    list.push(appFolder + '/controller/' + controllerName.split('.').join('/') + '.js');
                });
                Zen.each(views, function (viewName) {
                    list.push(appFolder + '/view/' + viewName.split('.').join('/') + '.js');
                });
                Zen.each(models, function (modelName) {
                    list.push(appFolder + '/model/' + modelName.split('.').join('/') + '.js');
                });
                Zen.Loader.load(list, beforeLaunch);
            }
        }
    };
    Zen.Application.getAllControllers = function () {
        return controllerCache;
    };

    Zen.Loader = {
        _path: ''
    };
    Zen.Loader.setPath = function (path) {
        /**
         * @type {String}
         * @private
         */
        this._path = path;
    };
    Zen.Loader.load = function (list, callback) {
        var path = this._path;
        var count = 0;
        var callbackFn = function () {};
        if (typeof callback == 'function') {
            callbackFn = callback;
        } else if (typeof callback.fn == 'function') {
            callbackFn = callback.fn;
        }

        Zen.each(list, function (file) {
            count++;

            var script = document.createElement('script');
            script.src = path + file;
            script.type = 'text/javascript';
            script.onload = function () {
                count--;
                if (count == 0) {
                    callbackFn.call(callback.scope || window);
                }
            };
            script.onerror = function () {
                count--;
                if (count == 0) {
                    callbackFn.call(callback.scope || window);
                }
                console.log('Load file ' + path + file + ' failed.');
            };
            document.getElementsByTagName('head')[0].appendChild(script);
        });
    };

    /**
     * export
     * @type {Zen}
     */
    window.Zen = Zen;
})(window, jQuery);

